// balm_metricsample.h                                                -*-C++-*-
#ifndef INCLUDED_BALM_METRICSAMPLE
#define INCLUDED_BALM_METRICSAMPLE

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a container for a sample of collected metric records.
//
//@CLASSES:
// balm::MetricSampleGroup: a group of records describing the same time period
//      balm::MetricSample: a sample of collected metric records
//
//@SEE_ALSO: balm_publisher, balm_metricrecord
//
//@DESCRIPTION: This component provides a container used to store a sample of
// recorded metric information.  A `balm::MetricSample` contains a collection
// of addresses to (external) `balm::MetricRecord` objects containing the
// aggregated record values for a series of metrics.  The records in a sample
// are broken into a series of groups, each group is represented by a
// `balm::MetricSampleGroup` object.  Each `balm::MetricSampleGroup` contains
// a sequence of records and an elapsed time value, indicating the time period
// over which those records were collected.  Finally, a `balm::MetricSample`
// object contains a timestamp value used to indicate when the sample was
// taken.
//
///Alternative Systems for Telemetry
///---------------------------------
// Bloomberg software may alternatively use the GUTS telemetry API, which is
// integrated into Bloomberg infrastructure.
//
///Thread Safety
///-------------
// `balm::MetricSample` and `balm::MetricSampleGroup` are both *const*
// *thread-safe*, meaning that accessors may be invoked concurrently from
// different threads, but it is not safe to access or modify an object in one
// thread while another thread modifies the same object.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Basic Usage
/// - - - - - - - - - - -
// The following example demonstrates how to create and use a metric sample.
// We start by initializing several `balm::MetricRecord` values, which we will
// add to the sample.  Note that in this example we create the `balm::MetricId`
// objects by hand; however, in practice ids should be obtained from a
// `balm::MetricRegistry` object (such as the one owned by a
// `balm::MetricsManager`).
// ```
// bslma::Allocator *allocator = bslma::Default::allocator(0);
//
// balm::Category myCategory("MyCategory");
// balm::MetricDescription descA(&myCategory, "MetricA");
// balm::MetricDescription descB(&myCategory, "MetricB");
// balm::MetricDescription descC(&myCategory, "MetricC");
//
// balm::MetricId metricA(&descA);
// balm::MetricId metricB(&descB);
// balm::MetricId metricC(&descC);
//
// const int TZ = 0;  // UTC time zone offset
//
// bdlt::DatetimeTz timeStamp(bdlt::Datetime(2008, 3, 26, 13, 30, 0, 0), TZ);
// balm::MetricRecord recordA(metricA, 0, 0, 0, 0);
// balm::MetricRecord recordB(metricB, 1, 2, 3, 4);
// balm::MetricRecord recordC(metricC, 4, 3, 2, 1);
// ```
// Now we create the two arrays of metric records whose addresses we will
// later add to the metric sample:
// ```
// balm::MetricRecord              buffer1[] = { recordA, recordB };
// bsl::vector<balm::MetricRecord> buffer2(allocator);
// buffer2.push_back(recordC);
// ```
// Next we create a `balm::MetricSample` object, `sample`, and set its
// timestamp property.  Then we add two groups of records (containing the
// addresses of our two record arrays) to the sample we have created.  Since
// the records were not actually collected over a period of time, we supply an
// arbitrary elapsed time value of 1 second and 2 seconds (respectively) for
// the two groups added to the sample.  Note that these arrays must remain
// valid for the lifetime of `sample`.
// ```
// balm::MetricSample sample(allocator);
// sample.setTimeStamp(timeStamp);
// sample.appendGroup(buffer1,
//                    sizeof(buffer1) / sizeof(*buffer1),
//                    bsls::TimeInterval(1.0));
// sample.appendGroup(buffer2.data(),
//                    static_cast<int>(buffer2.size()),
//                    bsls::TimeInterval(2.0));
// ```
// We can verify the basic properties of our sample:
// ```
//      assert(timeStamp             == sample.timeStamp());
//      assert(2                     == sample.numGroups());
//      assert(3                     == sample.numRecords());
//      assert(bsls::TimeInterval(1) == sample.sampleGroup(0).elapsedTime());
//      assert(buffer1               == sample.sampleGroup(0).records());
//      assert(2                     == sample.sampleGroup(0).numRecords());
//      assert(bsls::TimeInterval(2) == sample.sampleGroup(1).elapsedTime());
//      assert(buffer2.data()        == sample.sampleGroup(1).records());
//      assert(1                     == sample.sampleGroup(1).numRecords());
// ```
// Finally we can obtain an iterator over the sample's sequence of groups.  In
// this simple example, we iterate over the groups of records in the sample
// and, for each group, iterate over the records in that group, writing those
// records to the console.
// ```
// balm::MetricSample::const_iterator sampleIt = sample.begin();
// for ( ; sampleIt != sample.end(); ++sampleIt) {
//     balm::MetricSampleGroup::const_iterator groupIt = sampleIt->begin();
//     for ( ; groupIt != sampleIt->end(); ++groupIt) {
//         bsl::cout << *groupIt << bsl::endl;
//     }
// }
// ```
// The output will look like:
// ```
// [ MyCategory.MetricA: 0 0 0 0 ]
// [ MyCategory.MetricB: 1 2 3 4 ]
// [ MyCategory.MetricC: 4 3 2 1 ]
// ```

#include <balscm_version.h>

#include <balm_metricrecord.h>

#include <bdlt_datetimetz.h>

#include <bsls_timeinterval.h>

#include <bslma_allocator.h>

#include <bslmf_nestedtraitdeclaration.h>

#include <bsl_iosfwd.h>
#include <bsl_vector.h>

namespace BloombergLP {

namespace balm {
                          // =======================
                          // class MetricSampleGroup
                          // =======================

/// This class provides an *in-core* value-semantic representation of a
/// group of metric record values.  This class contains the address of an
/// array of (externally managed) `MetricRecord` objects, the number of
/// records in that array, and an elapsed time value (used to indicate the
/// time span over which the metric values were aggregated).
class MetricSampleGroup {

    // DATA
    const MetricRecord *d_records_p;   // array of records (held, not
                                       // owned)

    int                 d_numRecords;  // number of records in array

    bsls::TimeInterval  d_elapsedTime; // interval described by records

  public:
    // PUBLIC TYPES

    /// A `const_iterator` is an alias for an iterator over the
    /// non-modifiable records referenced in a `MetricSampleGroup`.
    typedef const MetricRecord *const_iterator;

    // CREATORS

    /// Create an empty sample group.  By default, the `records()` address
    /// is 0, `numRecords()` is 0, and the `elapsedTime()` is the default-
    /// constructed `bsls::TimeInterval`.
    MetricSampleGroup();

    /// Create a sample group containing the specified sequence of
    /// `records` of specified length `numRecords`, recorded over a period
    /// whose duration is the specified `elapsedTime`.  The behavior is
    /// undefined unless `0 <= numRecords` and `records` points to a
    /// contiguous sequence of (at least) `numRecords` metric records.  Note
    /// that the contents of `records` is *not* copied and the supplied
    /// array must remain valid for the productive lifetime of this object
    /// or until the records are set to a different sequence by calling the
    /// `setRecords` manipulator.
    MetricSampleGroup(const MetricRecord             *records,
                           int                        numRecords,
                           const bsls::TimeInterval&  elapsedTime);

    /// Create a sample group having the same (in-core) value as the
    /// specified `original` sample group.
    MetricSampleGroup(const MetricSampleGroup& original);

    /// Destroy this object.
    //! ~MetricSampleGroup() = default;

    // MANIPULATORS

    /// Assign to this sample group the value of the specified `rhs` sample
    /// group, and return a reference to this modifiable sample group.
    /// Note that only the pointer to the `MetricRecord` array and the
    /// length are copied, and not the records themselves.
    MetricSampleGroup& operator=(const MetricSampleGroup& rhs);

    /// Set the elapsed time (used to indicate the time span over which
    /// this object's metric records were aggregated) to the specified
    /// `elapsedTime`.
    void setElapsedTime(const bsls::TimeInterval& elapsedTime);

    /// Set the sequence of records referred to by this sample group to the
    /// specified sequence of `records` of specified length `numRecords`.
    /// The behavior is undefined unless `0 <= numRecords`, and `records`
    /// refers to a contiguous sequence of (at least) `numRecords`.  Note
    /// that the contents of `records` is *not* copied and the supplied
    /// array must remain valid for the productive lifetime of this object
    /// or until the records are set to a different sequence by calling the
    /// `setRecords` manipulator.
    void setRecords(const MetricRecord *records, int numRecords);

    // ACCESSORS

    /// Return the address of the contiguous sequence of non-modifiable
    /// records of length `numRecords()`.
    const MetricRecord *records() const;

    /// Return the number of records (referenced to by `records()`) in this
    /// object.
    int numRecords() const;

    /// Return a reference to the non-modifiable elapsed time interval over
    /// which this object's metric records were aggregated.
    const bsls::TimeInterval& elapsedTime() const;

    /// Return an iterator positioned at the beginning of the sequence of
    /// `MetricRecord` objects referenced by this object.
    const_iterator begin() const;

    /// Return an iterator positioned one past the final `MetricRecord`
    /// object in the sequence of records referenced by this object.
    const_iterator end() const;

    /// Format this object to the specified output `stream` at the (absolute
    /// value of) the optionally specified indentation `level` and return a
    /// reference to `stream`.  If `level` is specified, optionally specify
    /// `spacesPerLevel`, the number of spaces per indentation level for
    /// this and all of its nested objects.  If `level` is negative,
    /// suppress indentation of the first line.  If `spacesPerLevel` is
    /// negative, format the entire output on one line, suppressing all but
    /// the initial indentation (as governed by `level`).  If `stream` is
    /// not valid on entry, this operation has no effect.
    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) const;
};

// FREE OPERATORS

/// Return `true` if the specified `lhs` and `rhs` sample groups have the
/// same value, and `false` otherwise.  Two sample groups have the same
/// value if the respective record sequence-addresses, number of records,
/// and elapsed time are the same.
bool operator==(const MetricSampleGroup& lhs,
                const MetricSampleGroup& rhs);

/// Return `true` if the specified `lhs` and `rhs` sample groups do not
/// have the same value, and `false` otherwise.  Two sample groups do not
/// have the same value if any of the respective record-sequence addresses,
/// number of records, or elapsed time, are not the same.
bool operator!=(const MetricSampleGroup& lhs,
                const MetricSampleGroup& rhs);

/// Write a formatted description of the specified `rhs` to the specified
/// `stream` and return a reference to the modifiable `stream`.
bsl::ostream& operator<<(bsl::ostream&            stream,
                         const MetricSampleGroup& rhs);

                             // ==================
                             // class MetricSample
                             // ==================

/// This class provides an *in-core* value-semantic representation of a
/// sample of metric values.  The class contains a collection of addresses
/// to (external) `MetricRecord` objects holding the values for their
/// respective metrics (aggregated over some period of time).  The metric
/// records contained by a sample are broken into a series of groups, which
/// are represented by `MetricSampleGroup` objects.  Each group contains a
/// sequence of records and an elapsed time value, indicating the period of
/// time over which those records were taken.  This class also provides a
/// timestamp value, used to indicate when the sample was collected.  The
/// class provides a method, `appendGroups`, that appends a group of metric
/// records to the sample.  Arrays supplied using `appendGroups` must be
/// valid for the productive lifetime of the `MetricSample` object or until
/// they are removed by calling `removeAllRecords`.
class MetricSample {

    // PRIVATE TYPES
    typedef MetricSampleGroup SampleGroup;

    // DATA
    bdlt::DatetimeTz           d_timeStamp;   // time the records were
                                             // collected

    bsl::vector<SampleGroup>  d_records;     // vector of groups of records

    int                       d_numRecords;  // total number of records

    // FRIENDS
    friend bool operator==(const MetricSample& lhs,
                           const MetricSample& rhs);

  public:
    // PUBLIC TYPES

    /// A `const_iterator` is an alias for an iterator over the
    /// non-modifiable sample groups contained in a `MetricSample`.
    typedef bsl::vector<MetricSampleGroup>::const_iterator const_iterator;

    // PUBLIC TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(MetricSample, bslma::UsesBslmaAllocator);

    // CREATORS

    /// Create an empty metric sample.  Optionally specify a
    /// `basicAllocator` used to supply memory.  If `basicAllocator` is 0,
    /// the currently installed default allocator is used.
    MetricSample(bslma::Allocator *basicAllocator = 0);

    /// Create a metric sample containing the same value as the specified
    /// `original` sample.  Optionally specify a `basicAllocator` used to
    /// supply memory.  If `basicAllocator` is 0, the currently installed
    /// default allocator is used.  Note that copying the contained
    /// `MetricSampleGroup` objects copies only the pointers to their
    /// respective `MetricRecord` arrays, and does not copy the records
    /// themselves; hence, these record arrays must remain valid for the
    /// productive lifetimes of all copied objects or until the records are
    /// removed by calling `removeAllRecords`.
    MetricSample(const MetricSample&  original,
                 bslma::Allocator    *basicAllocator = 0);

    /// Destroy this metric sample.
    ~MetricSample();

    // MANIPULATORS

    /// Assign to this sample the value of the specified `rhs` sample and
    /// return a reference to this modifiable sample.  Note that copying
    /// the `MetricSampleGroup` objects contained in `rhs` copies only the
    /// pointers to their respective `MetricRecord` arrays, and does not
    /// copy records themselves; hence, these record arrays must remain
    /// valid for the productive lifetimes of all copied objects or until
    /// records are removed by calling `removeAllRecords`.
    MetricSample& operator=(const MetricSample& rhs);

    /// Set the timestamp (used to indicate when the sample was taken) to
    /// the specified `timeStamp`.
    void setTimeStamp(const bdlt::DatetimeTz& timeStamp);

    /// Append the specified `group` of records to the sequence of groups
    /// maintained by this sample.  If `group.numRecords()` is 0 this method
    /// has no effect.  The behavior is undefined unless
    /// `group.elapsedTime() > bsls::TimeInterval(0, 0)`.  Note that the
    /// `MetricRecord` objects referred to by `records` are *not* copied:
    /// hence, the supplied array must remain valid for the productive
    /// lifetime of this object or until the group is removed by calling
    /// `removeAllRecords()`.
    void appendGroup(const MetricSampleGroup& group);

    /// Append to the sequence of groups maintained by this sample a new
    /// group containing the specified sequence of `records` of specified
    /// length `numRecords` measuring the specified `elapsedTime`.  The
    /// behavior is undefined unless `0 <= numRecords`, `records` refers to
    /// a contiguous sequence of size (at least) `numRecords`, and
    /// `elapsedTime > bsls::TimeInterval(0, 0)`.  Note that `records` is
    /// *not* copied: hence, the supplied array must remain valid for the
    /// lifetime of this object or until the records are removed by calling
    /// `removeAllRecords()`.  This method is functionally equivalent to:
    /// ```
    /// appendGroup(MetricSampleGroup(records, numRecords, elapsedTime));
    /// ```
    void appendGroup(const MetricRecord        *records,
                     int                        numRecords,
                     const bsls::TimeInterval&  elapsedTime);

    /// Remove all metric records from this sample.
    void removeAllRecords();

    // ACCESSORS

    /// Return a reference to the non-modifiable `MetricSampleGroup` object
    /// at the specified `index` in this sample.  The behavior is undefined
    /// unless `0 <= index < numGroups()`.  Note that the returned
    /// reference will remain valid until this sample is modified by
    /// invoking `appendGroup` or `removeAllRecords()`.
    const MetricSampleGroup& sampleGroup(int index) const;

    /// Return a reference to the non-modifiable timestamp for this sample.
    const bdlt::DatetimeTz& timeStamp() const;

    /// Return an iterator positioned at the beginning of the sequence of
    /// `MetricSampleGroup` objects contained by this object.  Note that the
    /// iterator will remain valid until this sample is modified by invoking
    /// either `appendGroups` or `removeAllRecords()`.
    const_iterator begin() const;

    /// Return an iterator positioned one one past the final
    /// `MetricSampleGroup` object in the sequence of sample groups
    /// contained by this object.  Note that the iterator will remain valid
    /// until this sample is modified by invoking `appendGroup` or
    /// `removeAllRecords()`.
    const_iterator end() const;

    /// Return the number of record groups (i.e., `MetricSampleGroup`
    /// objects) that are contained in this object.
    int numGroups() const;

    /// Return the total number of records in this sample (i.e., the sum of
    /// the lengths of all the appended record groups).
    int numRecords() const;

    /// Format this object to the specified output `stream` at the (absolute
    /// value of) the optionally specified indentation `level` and return a
    /// reference to `stream`.  If `level` is specified, optionally specify
    /// `spacesPerLevel`, the number of spaces per indentation level for
    /// this and all of its nested objects.  If `level` is negative,
    /// suppress indentation of the first line.  If `spacesPerLevel` is
    /// negative, format the entire output on one line, suppressing all but
    /// the initial indentation (as governed by `level`).  If `stream` is
    /// not valid on entry, this operation has no effect.
    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) const;
};

// FREE OPERATORS

/// Return `true` if the specified `lhs` and `rhs` samples have the same
/// value, and `false` otherwise.  Two samples have the same value if they
/// have the same timestamp value, contain the same number of record
/// groups, and if the respective groups of records at each index position
/// have the same value.
bool operator==(const MetricSample& lhs, const MetricSample& rhs);

/// Return `true` if the specified `lhs` and `rhs` samples do not have the
/// same value, and `false` otherwise.  Two samples do not have the same
/// value if they have different values for their timestamps, or number of
/// record groups, or if any of the groups of records at corresponding
/// indices have different values.
bool operator!=(const MetricSample& lhs, const MetricSample& rhs);

/// Write a formatted description of the specified `rhs` to the specified
/// `stream` and return a reference to the modifiable `stream`.
bsl::ostream& operator<<(bsl::ostream& stream, const MetricSample& rhs);

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

                          // -----------------------
                          // class MetricSampleGroup
                          // -----------------------

// CREATORS
inline
MetricSampleGroup::MetricSampleGroup()
: d_records_p(0)
, d_numRecords(0)
, d_elapsedTime()
{
}

inline
MetricSampleGroup::MetricSampleGroup(const MetricRecord        *records,
                                     int                        numRecords,
                                     const bsls::TimeInterval&  elapsedTime)
: d_records_p(records)
, d_numRecords(numRecords)
, d_elapsedTime(elapsedTime)
{
}

inline
MetricSampleGroup::MetricSampleGroup(const MetricSampleGroup& original)
: d_records_p(original.d_records_p)
, d_numRecords(original.d_numRecords)
, d_elapsedTime(original.d_elapsedTime)
{
}

// MANIPULATORS
inline
MetricSampleGroup& MetricSampleGroup::operator=(const MetricSampleGroup& rhs)
{
    d_records_p   = rhs.d_records_p;
    d_numRecords  = rhs.d_numRecords;
    d_elapsedTime = rhs.d_elapsedTime;
    return *this;
}

inline
void MetricSampleGroup::setElapsedTime(const bsls::TimeInterval& elapsedTime)
{
    d_elapsedTime = elapsedTime;
}

inline
void MetricSampleGroup::setRecords(const MetricRecord *records,
                                   int                 numRecords)
{
    d_records_p  = records;
    d_numRecords = numRecords;
}

// ACCESSORS
inline
const MetricRecord *MetricSampleGroup::records() const
{
    return d_records_p;
}

inline
int MetricSampleGroup::numRecords() const
{
    return d_numRecords;
}

inline
const bsls::TimeInterval& MetricSampleGroup::elapsedTime() const
{
    return d_elapsedTime;
}

inline
MetricSampleGroup::const_iterator MetricSampleGroup::begin() const
{
    return d_records_p;
}

inline
MetricSampleGroup::const_iterator MetricSampleGroup::end() const
{
    return d_records_p + d_numRecords;
}

}  // close package namespace

// FREE OPERATORS
inline
bool balm::operator==(const MetricSampleGroup& lhs,
                      const MetricSampleGroup& rhs)
{
    return lhs.records()     == rhs.records()
        && lhs.numRecords()  == rhs.numRecords()
        && lhs.elapsedTime() == rhs.elapsedTime();
}

inline
bool balm::operator!=(const MetricSampleGroup& lhs,
                      const MetricSampleGroup& rhs)
{
    return !(lhs == rhs);
}

inline
bsl::ostream& balm::operator<<(bsl::ostream&            stream,
                               const MetricSampleGroup& rhs)
{
    return rhs.print(stream, 0, -1);
}

namespace balm {
                             // ------------------
                             // class MetricSample
                             // ------------------

// CREATORS
inline
MetricSample::MetricSample(bslma::Allocator *basicAllocator)
: d_timeStamp()
, d_records(basicAllocator)
, d_numRecords(0)
{
}

inline
MetricSample::~MetricSample()
{
}

// MANIPULATORS
inline
void MetricSample::setTimeStamp(const bdlt::DatetimeTz& timeStamp)
{
    d_timeStamp = timeStamp;
}

inline
void MetricSample::appendGroup(const MetricSampleGroup& group)
{
    if (0 < group.numRecords()) {
        d_records.push_back(group);
        d_numRecords += group.numRecords();
    }
}

inline
void MetricSample::appendGroup(const MetricRecord        *records,
                               int                        numRecords,
                               const bsls::TimeInterval&  elapsedTime)
{
    if (0 < numRecords) {
        d_records.push_back(SampleGroup(records, numRecords, elapsedTime));
        d_numRecords += numRecords;
    }
}

inline
void MetricSample::removeAllRecords()
{
    d_records.clear();
    d_numRecords = 0;
}

// ACCESSORS
inline
const MetricSampleGroup& MetricSample::sampleGroup(int index) const
{
    return d_records[index];
}

inline
const bdlt::DatetimeTz& MetricSample::timeStamp() const
{
    return d_timeStamp;
}

inline
MetricSample::const_iterator MetricSample::begin() const
{
    return d_records.begin();
}

inline
MetricSample::const_iterator MetricSample::end() const
{
    return d_records.end();
}

inline
int MetricSample::numGroups() const
{
    return static_cast<int>(d_records.size());
}

inline
int MetricSample::numRecords() const
{
    return d_numRecords;
}
}  // close package namespace

// FREE OPERATORS
inline
bool balm::operator==(const MetricSample& lhs, const MetricSample& rhs)
{
    return lhs.d_timeStamp == rhs.d_timeStamp
        && lhs.d_records   == rhs.d_records;
}

inline
bool balm::operator!=(const MetricSample& lhs, const MetricSample& rhs)
{
    return !(lhs == rhs);
}

inline
bsl::ostream& balm::operator<<(bsl::ostream& stream, const MetricSample& rhs)
{
    return rhs.print(stream, 0, -1);
}

}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2015 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
